package eval.results;

import java.io.FileWriter;
import java.util.HashMap;
import java.util.Map;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import eval.deploy.IDemandManager;
import eval.deploy.IDeployManager;
import eval.engine.IPlacement;
import eval.host.HostCapacityEnum;
import eval.host.IHostManager;
import eval.utilities.configuration.ExperimentConfiguration;
import eval.validation.SanityCheck;
import eval.vm.IVMManager;

@Singleton
public class ResultManager implements IResultManager {

	private ExperimentConfiguration configuration = ExperimentConfiguration.getSingleton();
	
	private IPlacement placementHistory[];
	private double engineComputationTime[];
	private double tickComputationTime[];
	private ResultsData resultsData;
	private double totalExperimentTime;
	
	private SanityCheck sanityCheck;

	@Inject
	public ResultManager(IHostManager hostManager,IDeployManager deployManager,
			IVMManager vmManager,
			IDemandManager demandManager) {

		placementHistory = new IPlacement[configuration.experimentTicks];
		engineComputationTime = new double[configuration.experimentTicks];
		tickComputationTime = new double[configuration.experimentTicks];
		this.sanityCheck = new SanityCheck(deployManager, vmManager,demandManager);
	}
	
	private void postProcessResults() {
		int experimentTicks = configuration.experimentTicks;
		resultsData = new ResultsData(configuration.optimizationGoal.toString());
		resultsData.setExperimentTicks(experimentTicks);
		resultsData.setComputationTime(engineComputationTime);
		resultsData.setTotalExperimentTimeInSec(totalExperimentTime);
		int vmOnCloud = 0;
		int loadHostCost = 0;
		int baseHostCost = 0;
		int startUpHostCost = 0;
		double totalCost = 0.0;
		int incomeForDemand = 0;
		int balance = 0;
		int totalUnSatisfiedCpuDemand = 0;
		int totalSatisfiedCpuDemand = 0;
		double[] computationTime = new double[experimentTicks];
		double[] hostPowerOnEnergyConsumption = new double[experimentTicks];
		double[] hostBaseEnergyConsumption = new double[experimentTicks];
		double[] hostLoadEnergyConsumption = new double[experimentTicks];
		double[] totalConsumptione =  new double[experimentTicks];
		double[] incomeFromSatisfyDemand = new double[experimentTicks];
		double[] balanceOfIncome = new double[experimentTicks];
		int[] numberOfHostAtCloud = new int[experimentTicks];
		int[] numberOfOpenHostAtCloud = new int[experimentTicks];
		int[] numberOfCloseHostAtCloud = new int[experimentTicks];
		int[] numberOfHostTurnOnAtCloud = new int[experimentTicks];
		int[] numberOfHostTurnOffAtCloud = new int[experimentTicks];
		int[] numberOfHostCandidatesForClosing = new int[experimentTicks];
		int[] numberOfVmsAtCloud = new int[experimentTicks];
		int[] numberOfVmsDeploy = new int[experimentTicks];
		int[] numberOfVmsReject = new int[experimentTicks];
		int[] numberOfVmsRemove = new int[experimentTicks];
		int[] numberOfVmsReqToBeDeploy = new int[experimentTicks];
		int[] totalCpuCapcityAtCloud = new int[experimentTicks];
		int[] totalCpuCapcityAtOpenHosts = new int[experimentTicks];
		int[] totalCpuUseAtCloud = new int[experimentTicks];
		int[] totalCpuFreeAtCloud = new int[experimentTicks];
		int[] totalCpuDemandAtCloud = new int[experimentTicks];
		int[] totalDemandSatisfiedAtCloud = new int[experimentTicks];
		int[] totalDemandUnSatisfiedAtCloud = new int[experimentTicks];
		int[] totalMemCapcityAtCloud = new int[experimentTicks];
		int[] totalMemCapcityAtOpenHosts = new int[experimentTicks];
		int[] totalMemUseAtCloud = new int[experimentTicks];
		int[] totalMemFreeAtCloud = new int[experimentTicks];
		int[] totalMemDemandAtCloud = new int[experimentTicks];
		int[] numberOfVmsThatHaveFullSatisfied = new int[experimentTicks];
		int[] numberOfVmsThatHaveUnSatisfied = new int[experimentTicks];
		int[] vmRelocate = new int[experimentTicks];
		double[] sumOfMinREservtion = new double[experimentTicks];
				
		for (int tick = 0; tick < configuration.experimentTicks; tick++) {
			IPlacement placement = placementHistory[tick];
			
			totalUnSatisfiedCpuDemand += placement.getTotalUnSatisfiedCPUDemand();
			totalSatisfiedCpuDemand += placement.getTotalSatisfiedCPUDemand();
			numberOfOpenHostAtCloud[tick] = placement.getNumberOfOpenHosts();
			numberOfCloseHostAtCloud[tick] = placement.getNumberOfClosedHosts();
			numberOfHostAtCloud[tick] = numberOfCloseHostAtCloud[tick]+numberOfOpenHostAtCloud[tick];
			numberOfHostTurnOnAtCloud[tick] = placement.getNumberOfTurnOnHosts();
			numberOfHostTurnOffAtCloud[tick] = placement.getNumberOfTurnOffHosts();
			numberOfHostCandidatesForClosing[tick] = placement.getNumberOfCandidatesForClosingHosts();
			numberOfVmsDeploy[tick] = placement.getNumberOfPlacedVMs();
			numberOfVmsReqToBeDeploy[tick] = placement.getNumberOfVmsReqToBeDeploy();
			numberOfVmsRemove[tick] = placement.getNumberOfRemovedVMs();
			vmOnCloud += numberOfVmsDeploy[tick];
			vmOnCloud -= numberOfVmsRemove[tick];
			numberOfVmsReject[tick] = numberOfVmsReqToBeDeploy[tick] - numberOfVmsDeploy[tick];
			numberOfVmsAtCloud[tick] = vmOnCloud;
			totalCpuCapcityAtOpenHosts[tick] = placement.getTotalCpuCapcityAtOpenHosts();
			totalCpuCapcityAtCloud[tick] = placement.getTotalCpuCapcityAtCloud();
			totalCpuUseAtCloud[tick] = placement.getTotalCloudCPULoad();
			totalCpuFreeAtCloud[tick] = totalCpuCapcityAtOpenHosts[tick]-totalCpuUseAtCloud[tick];
			totalCpuDemandAtCloud[tick] = placement.getTotalCloudCpuDemand();
			totalDemandSatisfiedAtCloud[tick] = placement.getTotalSatisfiedCPUDemand();
			totalDemandUnSatisfiedAtCloud[tick] = placement.getTotalUnSatisfiedCPUDemand();
			totalMemCapcityAtCloud[tick] = placement.getTotalMemLoadAtCloud();
			totalMemCapcityAtOpenHosts[tick] = placement.getTotalMemCapcityAtOpenHosts();
//			totalMemUseAtCloud[tick] = placement.getTotalCloudMemAtCloud();
			totalMemUseAtCloud[tick] = placement.getTotalMemLoadAtCloud();
			totalMemFreeAtCloud[tick] = totalMemCapcityAtOpenHosts[tick]-totalMemUseAtCloud[tick];
			totalMemDemandAtCloud[tick] = placement.getTotalCloudMemDemand();
			numberOfVmsThatHaveFullSatisfied[tick] = placement.getNumberOfVMsWithSatisfiedDemand();
			numberOfVmsThatHaveUnSatisfied[tick] = placement.getNumberOfVMsWithUnSatisfiedDemand();
			vmRelocate[tick] = placement.getVmRelocate();
			
			startUpHostCost =	(int) placement.getHostsStartUpCost();
			loadHostCost 	= 	(int) placement.getWorkingHostCost();
			baseHostCost	= 	(int) placement.getBaseHostCost();
			totalCost =  startUpHostCost+loadHostCost+baseHostCost;
			incomeForDemand = 	(int) placement.getIncomeForSatisfyDemand();
			balance 		=  (incomeForDemand-(startUpHostCost+loadHostCost+baseHostCost));
			totalConsumptione[tick] = totalCost+(tick==0?0:totalConsumptione[tick-1]);
			hostPowerOnEnergyConsumption[tick] = startUpHostCost+(tick==0?0:totalConsumptione[tick-1]) ;
			hostBaseEnergyConsumption[tick] = baseHostCost+(tick==0?0:hostBaseEnergyConsumption[tick-1]) ;
			hostLoadEnergyConsumption[tick] =  loadHostCost+(tick==0?0:hostLoadEnergyConsumption[tick-1]) ;
			incomeFromSatisfyDemand[tick] =  incomeForDemand+(tick==0?0:incomeFromSatisfyDemand[tick-1]) ;
			balanceOfIncome[tick] =   balance+(tick==0?0:balanceOfIncome[tick-1]) ; 
			sumOfMinREservtion[tick] = placement.getSumOfMinReservtion()/totalCpuCapcityAtCloud[tick];
		}
		
		resultsData.setComputationTime(computationTime);
		resultsData.setExperimentTicks(experimentTicks);
		resultsData.setNumberOfCloseHostAtCloud(numberOfCloseHostAtCloud);
		resultsData.setNumberOfHostAtCloud(numberOfHostAtCloud);
		resultsData.setNumberOfHostCandidatesForClosing(numberOfHostCandidatesForClosing);
		resultsData.setNumberOfHostTurnOffAtCloud(numberOfHostTurnOffAtCloud);
		resultsData.setNumberOfHostTurnOnAtCloud(numberOfHostTurnOnAtCloud);
		resultsData.setNumberOfOpenHostAtCloud(numberOfOpenHostAtCloud);
		resultsData.setNumberOfVmsAtCloud(numberOfVmsAtCloud);
		resultsData.setNumberOfVmsDeploy(numberOfVmsDeploy);
		resultsData.setNumberOfVmsReject(numberOfVmsReject);
		resultsData.setNumberOfVmsRemove(numberOfVmsRemove);
		resultsData.setNumberOfVmsReqToBeDeploy(numberOfVmsReqToBeDeploy);
		resultsData.setNumberOfVmsThatHaveFullSatisfied(numberOfVmsThatHaveFullSatisfied);
		resultsData.setNumberOfVmsThatHaveUnSatisfied(numberOfVmsThatHaveUnSatisfied);
		resultsData.setTotalCpuCapcityAtCloud(totalCpuCapcityAtCloud);
		resultsData.setTotalCpuCapcityAtOpenHosts(totalCpuCapcityAtOpenHosts);
		resultsData.setTotalCpuDemandAtCloud(totalCpuDemandAtCloud);
		resultsData.setTotalCpuFreeAtCloud(totalCpuFreeAtCloud);
		resultsData.setTotalCpuUseAtCloud(totalCpuUseAtCloud);
		resultsData.setTotalDemandSatisfiedAtCloud(totalDemandSatisfiedAtCloud);
		resultsData.setTotalMemCapcityAtCloud(totalMemCapcityAtCloud);
		resultsData.setTotalMemCapcityAtOpenHosts(totalMemCapcityAtOpenHosts);
		resultsData.setTotalMemDemandAtCloud(totalMemDemandAtCloud);
		resultsData.setTotalMemFreeAtCloud(totalMemFreeAtCloud);
		resultsData.setTotalMemUseAtCloud(totalMemUseAtCloud);
		resultsData.setTotalDemandUnSatisfiedAtCloud(totalDemandUnSatisfiedAtCloud);
		resultsData.setVmRelocate(vmRelocate);
		
		resultsData.setPowerOnEnergyConsumption(hostPowerOnEnergyConsumption);
		resultsData.setBaseEnergyConsumption(hostBaseEnergyConsumption);
		resultsData.setLoadEnergyConsumption(hostLoadEnergyConsumption);
		resultsData.setIncomeFromSatisfyDemand(incomeFromSatisfyDemand);
		resultsData.setBalanceOfIncome(balanceOfIncome);
		resultsData.setTotalConsumptione(totalConsumptione);
		resultsData.setSumOfMinREservtion(sumOfMinREservtion);
	}
	
	@Override
	public void addPlacementAtTick(IPlacement placement, int tick) {
		placementHistory[tick] = placement;		
	}

	@Override
	public void addExperimentTotalTime(double experimentTime) {
		this.totalExperimentTime = experimentTime;		
	}

	@Override
	public void setExecutionTimesAtTick(long tickStartTime,double engineTimeInSec, int tick) {
		tickComputationTime[tick] = (System.currentTimeMillis() - tickStartTime) / 1000.0;
		engineComputationTime[tick] = engineTimeInSec;		
	}

	@Override
	public IResultsData getResultsData() {
		if (resultsData == null)
			postProcessResults();
		return resultsData;
	}
	@Override
	public void performSaintyCheck(String resFileName){
		 try{
			 String fstream = configuration.errorLogFilePath;
			 fstream += ExperimentSerializer.generateFileNameForErrorLogToCsv(resFileName,this.resultsData);
			 FileWriter fout = new FileWriter(fstream);
				
		boolean isSaintyCheckFinishedWithError = false;
		for(int tick = 1 ; tick < configuration.experimentTicks ; tick++){
			if(sanityCheck.runSaintyCheck(placementHistory[tick], placementHistory[tick-1], tick ,fout)	)
				isSaintyCheckFinishedWithError = true;
		}
		if(isSaintyCheckFinishedWithError){
			System.err.println("\n\nSainty Check found at least one error during experiment," +
					"\nCheck log for validation result at: " + configuration.errorLogFilePath);
		}
		else{
			System.out.println("\n\nSainty Check ended without errors.");
		}
		fout.flush();
		fout.flush();
		fout.close();
		  }catch (Exception e){//Catch exception if any
		  System.err.println("Error: " + e.getMessage());
		  }
	}
	
	
	
}